#ifndef GL_VIEW
#define GL_VIEW

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "matrix.h"
#include "matrix_util.h"

/*
  #include <iostream>
  #include <fstream>
  #include "primitive.h"
  #include "intersect.h"
  #include "mesh.h"
  #include "aabbtree.h"
*/


//const float DEG2RAD = 3.141593f / 180;
const float FOV_Y = 60.0f;
//Initial values to set frustrum
const float INIT_LEFT = -1.0f;
const float INIT_RIGHT = 1.0f;
const float INIT_BOTTOM = -1.0f;
const float INIT_TOP = 1.0f;
const float INIT_NEAR = 0.0001f;
const float INIT_FAR = 100.0f;
//Initial values to set camera
const float CAMERA_AGL_X = 45.0f;     // pitch 
const float CAMERA_AGL_Y = -45.0f;    // heading 
const float CAMERA_DIST = -5.0f;    

enum VIEW_MODE {FILL_MODE, FRAME_MODE, POINT_MODE} ;
enum PROJECT_MODE {FRUSTUM_MODE, ORTHO_MODE } ;


class ViewParameter
{
public:
ViewParameter(): windowWidth (640), windowHeight(480), windowSizeChanged(true), 
		mouseLeftDown(false), mouseRightDown(false), mouseMiddleDown (false), mouseX(0), mouseY(0), 
		cameraAglX (CAMERA_AGL_X), cameraAglY(CAMERA_AGL_Y), cameraDis (CAMERA_DIST) ,  cameraX(0.0f), cameraY (0.0f),
		projectionLeft(INIT_LEFT), projectionRight(INIT_RIGHT), 
		projectionBottom(INIT_BOTTOM) , projectionTop(INIT_TOP),
		projectionNear (INIT_NEAR), projectionFar(INIT_FAR), 
		viewMode(FILL_MODE), projectMode (FRUSTUM_MODE){}
public:
	int windowWidth;
	int windowHeight;
	bool windowSizeChanged;

	bool mouseLeftDown;
	bool mouseRightDown;
	bool mouseMiddleDown ;

	int mouseX;
	int mouseY;

	float cameraAglX;
	float cameraAglY;
	float cameraDis ;
	float cameraX ;
	float cameraY ;

	float projectionLeft;
	float projectionRight;
	float projectionBottom;
	float projectionTop;
	float projectionNear;
	float projectionFar;

//	ViewMatrix<float> view_matrix ;

	VIEW_MODE viewMode; 
	PROJECT_MODE projectMode ;
} ;



class GLView 
{
public :
	GLView () /*:  m_mouseLeftDown(false), m_mouseRightDown (false), 
				   cameraAngleX (45.0f), cameraAngleY(-45.0f), cameraDistance(-5.0f)*/
	{

	}
	virtual ~ GLView ()
	{

	}
public :

	/**
	 * configure projection and viewport
	 */
	virtual void setViewport(int x, int y, int w, int h)
	{
		// set viewport to be the entire window
		glViewport((GLsizei)x, (GLsizei)y, (GLsizei)w, (GLsizei)h);


		/* Setup our viewport. */

		glMatrixMode( GL_PROJECTION );
		glLoadIdentity( );
		float ratio = (float) w / (float) h;
		gluPerspective( 60.0, ratio, 0.0001f, 100.0f );

		// switch to modelview matrix in order to set scene
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

	}

	/** 
	 * configure projection and viewport of sub window
	 */
	virtual void setViewportSub(int x, int y, int width, int height, float nearPlane, float farPlane)
	{
		// set viewport
		glViewport(x, y, width, height);
		glScissor(x, y, width, height);

		// set perspective viewing frustum
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(60.0, (float)(width)/height, nearPlane, farPlane); // FOV, AspectRatio, NearClip, FarClip

		// switch to modelview matrix in order to set scene
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	/** 
	 * configure projection and viewport of sub window
	 */
	virtual void setViewportSub(int x, int y, int width, int height, float left, float right, float bottom, float top, float front, float back)
	{
		// set viewport
		glViewport(x, y, width, height);
		glScissor(x, y, width, height);

		// set perspective viewing frustum
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
//		if(projectionMode == 0)
			glFrustum(left, right, bottom, top, front, back);
//		else
//			glOrtho(left, right, bottom, top, front, back);

		// switch to modelview matrix in order to set scene
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	void setViewMode (VIEW_MODE vm = FILL_MODE)
	{
		this->mainView.viewMode = vm ;
		switch (mainView.viewMode)
		{
		case FILL_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			glEnable(GL_DEPTH_TEST);
			glEnable(GL_CULL_FACE);
			break;
		case FRAME_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			glDisable(GL_DEPTH_TEST);
			glDisable(GL_CULL_FACE);
			break;
			//glEnable(GL_DEPTH_TEST);
			//glEnable(GL_CULL_FACE);
		case POINT_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
			//glDisable(GL_DEPTH_TEST);
			glDisable(GL_CULL_FACE);
			break;
		}
	}

	void init_light(void)
	{
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_NORMALIZE);

		// Light model parameters:
		// -------------------------------------------

		GLfloat lmKa[] = {0.0, 0.0, 0.0, 0.0 };
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmKa);

		glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0);
		glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0.0);

		// -------------------------------------------
		// Spotlight Attenuation

		GLfloat spot_direction[] = {1.0, -1.0, -1.0 };
		GLint spot_exponent = 30;
		GLint spot_cutoff = 180;

		glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
		glLighti(GL_LIGHT0, GL_SPOT_EXPONENT, spot_exponent);
		glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, spot_cutoff);

		GLfloat Kc = 1.0;
		GLfloat Kl = 0.0;
		GLfloat Kq = 0.0;

		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION,Kc);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, Kl);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, Kq);


		// ------------------------------------------- 
		// Lighting parameters:

		GLfloat light_pos[] = {0.0f, 5.0f, 5.0f, 1.0f};
                
		GLfloat light_Ka[]  = {0.8f, .8f, .8f, 1.0f};
		GLfloat light_Kd[]  = {1.0f, 1.0f, 1.0f, 1.0f};
		GLfloat light_Ks[]  = {.8f, .8f, .8f, 1.0f};

		//GLfloat light_Ka[]  = {1.0f, 0.5f, 0.5f, 1.0f};
		//GLfloat light_Kd[]  = {1.f, 0.1f, 0.1f, 1.0f};
		//GLfloat light_Ks[]  = {1.0f, 1.0f, 1.0f, 1.0f};

		glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_Ka);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_Kd);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_Ks);

		// -------------------------------------------
		// Material parameters:

		//GLfloat material_Ka[] = {0.5f, 0.0f, 0.0f, 1.0f};
		//GLfloat material_Kd[] = {0.4f, 0.4f, 0.5f, 1.0f};
		//GLfloat material_Ks[] = {0.8f, 0.8f, 0.0f, 1.0f};
		GLfloat material_Ka[] = {.5f, .5f, .5f, 1.0f};
		GLfloat material_Kd[] = {0.4f, 0.4f, 0.4f, 1.0f};
		GLfloat material_Ks[] = {0.8f, 0.8f, 0.8f, 1.0f};
		GLfloat material_Ke[] = {0.1f, 0.1f, 0.1f, 0.0f};
		GLfloat material_Se = 20.0f;

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_Ka);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_Kd);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_Ks);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_Ke);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material_Se);

	}
	virtual void init(int _width = 1024, int _height = 768)
	{


		glShadeModel(GL_SMOOTH);                        // shading mathod: GL_SMOOTH or GL_FLAT
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);          // 4-byte pixel alignment

		// enable /disable features
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
		//glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_CULL_FACE);
		glEnable(GL_BLEND);
		glEnable(GL_SCISSOR_TEST);
		glClearColor( 0, 0, 0, 0 );
		
		init_light();
		this->mainView.windowWidth = _width;
		this->mainView.windowHeight = _height; 
		setViewport(0,0, _width, _height);
	}

	virtual void drawSub1 () 
	{
		// Set the left view
		setViewportSub(0, 0, mainView.windowWidth/2, mainView.windowHeight, mainView.projectionNear, mainView.projectionFar);
		setViewMode(FILL_MODE);
		glPushMatrix();
		glTranslatef(0, 0, mainView.cameraDis);
		glRotatef(mainView.cameraAglX, 1, 0, 0); // pitch
		glRotatef(mainView.cameraAglY, 0, 1, 0); // heading

		glClearColor(0.0f, 0.0f, 0.0f, 1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		draw_grid(10, 1);
		sence();

	}

	virtual void drawSub2 () 
	{
		// Set the left view
		setViewportSub(mainView.windowWidth/2, 0, mainView.windowWidth/2, mainView.windowHeight, mainView.projectionNear, mainView.projectionFar);
		setViewMode(FRAME_MODE);
		glClearColor(0.0f, 0.0f, 0.0f, 1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		glTranslatef( 0.0, 0.0, -5.0 );
		glRotatef(45.0f, 1, 0, 0); // pitch
		glRotatef(-45.0f, 0, 1, 0); // heading
		draw_grid(10, 1);
		sence();
	}
	virtual void onDraw( void )
	{
		drawSub1();
		drawSub2();

	}


	void draw_grid(float size, float step)
	{
		// disable lighting
		glDisable(GL_LIGHTING);
		glBegin(GL_LINES);
		glColor3f(0.3f, 0.3f, 0.3f);
		float offset = 0.0f;
		for(float i=step; i <= size; i+= step)
		{
			glVertex3f(-size, offset,  i);   // lines parallel to X-axis
			glVertex3f( size, offset,  i);
			glVertex3f(-size, offset, -i);   // lines parallel to X-axis
			glVertex3f( size, offset, -i);

			glVertex3f( i, offset, -size);   // lines parallel to Z-axis
			glVertex3f( i, offset,  size);
			glVertex3f(-i, offset, -size);   // lines parallel to Z-axis
			glVertex3f(-i, offset,  size);
		}

		// x-axis
		glColor3f(0.5f, offset, 0);
		glVertex3f(-size, offset, 0);
		glVertex3f( size, offset, 0);

		// z-axis
		glColor3f(0,offset,0.5f);
		glVertex3f(0, offset, -size);
		glVertex3f(0, offset,  size);

		glEnd();
		//glLineWidth(1);
		// enable lighting back
		glEnable(GL_LIGHTING);

		//drawCylinder();

		glFlush();
	}
	void setMousePos (int _x, int _y)
	{
		mainView.mouseX = _x; 
		mainView.mouseY = _y;
	}

	void setLButtonDown (bool flag = true)
	{
		this->mainView.mouseLeftDown = flag;
	}
	void setRButtonDown (bool flag = true)
	{
		this->mainView.mouseRightDown = flag;
	}

	void setMButtonDown (bool flag = true)
	{
		this->mainView.mouseMiddleDown = flag;
	}
	virtual void rotateCamera (int x, int y)
	{
//		if(m_mouseLeftDown)
//		{
		mainView.cameraAglY += (x - mainView.mouseX);
		mainView.cameraAglX += (y - mainView.mouseY);
		mainView.mouseX = x;
		mainView.mouseY = y;
//		}
	}
	virtual void zoomCamera(int delta)
	{
//		if(m_mouseRightDown)
//		{
		mainView.cameraDis -= (delta - mainView.mouseY) * 0.01f;
		mainView.mouseY = delta;
//		}
	}

	virtual void resize (int w, int h)
	{
		mainView.windowWidth = w; 
		mainView.windowHeight = h ;
		setViewport(0, 0, w, h);
	}

	virtual void sence ()
	{


		static float angle = 0.0f;

		/* Move down the z-axis. */
//	


		/* Rotate. */
//		glRotatef( angle, 0.0, 1.0, 0.0 );

		if( true ) 
		{

			if( ++angle > 360.0f ) {
				angle = 0.0f;
			}

		}

		static GLfloat v0[] = { -1.0f, -1.0f,  1.0f };
		static GLfloat v1[] = {  1.0f, -1.0f,  1.0f };
		static GLfloat v2[] = {  1.0f,  1.0f,  1.0f };
		static GLfloat v3[] = { -1.0f,  1.0f,  1.0f };
		static GLfloat v4[] = { -1.0f, -1.0f, -1.0f };
		static GLfloat v5[] = {  1.0f, -1.0f, -1.0f };
		static GLfloat v6[] = {  1.0f,  1.0f, -1.0f };
		static GLfloat v7[] = { -1.0f,  1.0f, -1.0f };
		static GLubyte red[]    = { 255,   0,   0, 255 };
		static GLubyte green[]  = {   0, 255,   0, 255 };
		static GLubyte blue[]   = {   0,   0, 255, 255 };
		static GLubyte white[]  = { 255, 255, 255, 255 };
		static GLubyte yellow[] = {   0, 255, 255, 255 };
		static GLubyte black[]  = {   0,   0,   0, 255 };
		static GLubyte orange[] = { 255, 255,   0, 255 };
		static GLubyte purple[] = { 255,   0, 255,   0 };


		/* Send our triangle data to the pipeline. */
		glBegin( GL_TRIANGLES );

		glNormal3f (0, 0, 1);
		glColor4ubv( red );
		glVertex3fv( v0 );
		glColor4ubv( green );
		glVertex3fv( v1 );
		glColor4ubv( blue );
		glVertex3fv( v2 );

		glNormal3f (0, 0, 1);
		glColor4ubv( red );
		glVertex3fv( v0 );
		glColor4ubv( blue );
		glVertex3fv( v2 );
		glColor4ubv( white );
		glVertex3fv( v3 );

		glNormal3f (1,0,0);
		glColor4ubv( green );
		glVertex3fv( v1 );
		glColor4ubv( black );
		glVertex3fv( v5 );
		glColor4ubv( orange );
		glVertex3fv( v6 );

		glNormal3f (1,0,0);
		glColor4ubv( green );
		glVertex3fv( v1 );
		glColor4ubv( orange );
		glVertex3fv( v6 );
		glColor4ubv( blue );
		glVertex3fv( v2 );

		glNormal3f (0,0,-1);
		glColor4ubv( black );
		glVertex3fv( v5 );
		glColor4ubv( yellow );
		glVertex3fv( v4 );
		glColor4ubv( purple );
		glVertex3fv( v7 );

		glNormal3f (0,0,-1);
		glColor4ubv( black );
		glVertex3fv( v5 );
		glColor4ubv( purple );
		glVertex3fv( v7 );
		glColor4ubv( orange );
		glVertex3fv( v6 );

		glNormal3f (-1,0, 0);
		glColor4ubv( yellow );
		glVertex3fv( v4 );
		glColor4ubv( red );
		glVertex3fv( v0 );
		glColor4ubv( white );
		glVertex3fv( v3 );

		glNormal3f (-1,0, 0);
		glColor4ubv( yellow );
		glVertex3fv( v4 );
		glColor4ubv( white );
		glVertex3fv( v3 );
		glColor4ubv( purple );
		glVertex3fv( v7 );

		glNormal3f( 0, 1, 0);
		glColor4ubv( white );
		glVertex3fv( v3 );
		glColor4ubv( blue );
		glVertex3fv( v2 );
		glColor4ubv( orange );
		glVertex3fv( v6 );

		glNormal3f( 0, 1, 0);
		glColor4ubv( white );
		glVertex3fv( v3 );
		glColor4ubv( orange );
		glVertex3fv( v6 );
		glColor4ubv( purple );
		glVertex3fv( v7 );

		glNormal3f( 0, -1, 0);
		glColor4ubv( green );
		glVertex3fv( v1 );
		glColor4ubv( red );
		glVertex3fv( v0 );
		glColor4ubv( yellow );
		glVertex3fv( v4 );

		glNormal3f( 0, -1, 0);
		glColor4ubv( green );
		glVertex3fv( v1 );
		glColor4ubv( yellow );
		glVertex3fv( v4 );
		glColor4ubv( black );
		glVertex3fv( v5 );

		glEnd( );		

	}

private :
	
protected :
//	int projectionMode ; 
	/*
	int m_winwidth ;
	int m_winheight ;

	int m_mouse_x;
	int m_mouse_y;

	bool m_mouseLeftDown ;
	bool m_mouseRightDown ;
	bool m_mouseMiddleDown ;

	float cameraAngleX; 
	float cameraAngleY; 
	float cameraDistance;

	VIEW_MODE m_view_mode ;
	*/

	ViewParameter mainView ;

};

#endif
